package tinyinventor;

import java.awt.BorderLayout;	
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.FileWriter;
import java.util.Collection;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import tinyinventor.compiler.CompileException;
import tinyinventor.compiler.Compiler;
import tinyinventor.compiler.NescCompiler;
import tinyinventor.compiler.PythonCompiler;
import workspace.SearchBar;
import workspace.SearchableContainer;
import workspace.Subset;
import workspace.TrashCan;
import workspace.WorkspaceEvent;
import workspace.WorkspaceListener;
import workspace.ZoomSlider;
import workspace.Workspace;
import codeblocks.BlockConnectorShape;
import codeblocks.BlockGenus;
import codeblocks.BlockLinkChecker;
import codeblocks.CommandRule;

/**
 * The TinyController initializes the workspace, sets up the UI,
 * loads the blocks and components from XML, and call's the compiler when
 * needed.
 * 
 * @author Morten Tranberg Hansen
 *
 */
public class Controller {

	private static final String PROJECT_FILE_NAME = "project.ti";

	// The blocks language definition  
	private static Element langDefRoot;

	//The single instance of the Workspace Controller
	protected static Workspace workspace;

	protected JPanel workspacePanel;

	// flag to indicate if a workspace has been loaded/initialized
	private boolean workspaceLoaded = false;

	/**
	 * Constructs a Controller instance that manages the interaction
	 * with the codeblocks.Workspace
	 * 
	 */
	public Controller() {
		workspace = Workspace.getInstance();

		// add trashcan and prepare trashcan images
		ImageIcon tc = new ImageIcon("res/images/trash.png");
		ImageIcon openedtc = new ImageIcon("res/images/trash_open.png");
		TrashCan trash = new TrashCan(tc.getImage(), openedtc.getImage());
		workspace.addWidget(trash, true, true);

		workspacePanel = new JPanel();
		workspacePanel.setLayout(new BorderLayout());
		workspacePanel.add(workspace, BorderLayout.CENTER);
	}

	
	/**
	 * Sets the file path for the language definition file, if the language
	 * definition file is located in
	 * 
	 * @param path the path to the blocks language definition file
	 */
	public void setLangDefFilePath(String path) {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		Document doc;
		try {
			builder = factory.newDocumentBuilder();

			doc = builder.parse(new File(path));

			langDefRoot = doc.getDocumentElement();

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}


	/**
	 * Loads all the block genuses, properties, and link rules of a language
	 * specified in the pre-defined language def file.
	 * 
	 * @param root Loads the language specified in the Element root
	 */
	private void loadBlockLanguage(Element root) {
		// load connector shapes
		// MUST load shapes before genuses in order to initialize connectors
		// within
		// each block correctly
		BlockConnectorShape.loadBlockConnectorShapes(root);

		// load genuses
		BlockGenus.loadBlockGenera(root);

		// load rules
		BlockLinkChecker.addRule(new CommandRule());
		BlockLinkChecker.addRule(new SocketRule());
		//BlockLinkChecker.addRule(new ParamProcedureRule());
		//BlockLinkChecker.addRule(new ParamAssignmentRule());
		//BlockLinkChecker.addRule(new TypeRule());
		BlockLinkChecker.addRule(new ScopeRule());
		

	}

	/**
	 * Resets the current language within the active Workspace.
	 * 
	 */
	public void resetLanguage() {
		// clear shape mappings
		BlockConnectorShape.resetConnectorShapeMappings();
		// clear block genuses
		BlockGenus.resetAllGenuses();
		// clear all link rules
		BlockLinkChecker.reset();
	}

	/**
	 * Loads a fresh workspace based on the default specifications in the
	 * language definition file. The block canvas will have no live blocks.
	 */
	public void loadFreshWorkspace(boolean loadBlockLanguage) {
		// need to just reset workspace (no need to reset language) unless
		// language was never loaded
		// reset only if workspace actually exists
		if (workspaceLoaded)
			resetWorkspace();

		if (loadBlockLanguage) {
			loadBlockLanguage(langDefRoot);
		}

		workspace.loadWorkspaceFrom(null, langDefRoot);

		workspaceLoaded = true;
	}
	

	/**
	 * Resets the entire workspace. This includes all blocks, pages, drawers,
	 * and trashed blocks. Also resets the undo/redo stack. The language (i.e.
	 * genuses and shapes) is not reset.
	 */
	public void resetWorkspace() {
		// clear all pages and their drawers
		// clear all drawers and their content
		// clear all block and renderable block instances
		workspace.reset();
		// clear action history
		// rum.reset();
		// clear runblock manager data
		// rbm.reset();
	}
	
	/**
	 * Returns the save string for the entire workspace. This includes the block
	 * workspace, any custom factories, canvas view state and position, pages
	 * 
	 * @return the save string for the entire workspace.
	 */
	public String getSaveString() {
		StringBuffer saveString = new StringBuffer();
		// append the save data
		// saveString.append("<?xml version=\"1.0\" encoding=\"UTF-16\"?>");
		// saveString.append("\r\n");
		// dtd file path may not be correct...
		// saveString.append("<!DOCTYPE StarLogo-TNG SYSTEM \""+SAVE_FORMAT_DTD_FILEPATH+"\">");
		// append root node
		saveString.append("<TINYINVENTOR>");
		saveString.append(workspace.getSaveString());
		saveString.append("</TINYINVENTOR>");
		return saveString.toString();
	}

	public boolean saveProjectToPath(String path) {
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(PROJECT_FILE_NAME));
			writer.write(getSaveString());
			writer.close();
			return true;
		} catch (IOException ex) {
			System.err.println(ex);
			return false;
		}
	}

	/**
	 * Loads the programming project from the specified file path. This method
	 * assumes that a Language Definition File has already been specified for
	 * this programming project.
	 * 
	 * @param path
	 *            String file path of the programming project to load
	 */
	public boolean loadProjectFromPath(String path) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		Document doc;
		try {
			builder = factory.newDocumentBuilder();

			doc = builder.parse(new File(path));

			Element projectRoot = doc.getDocumentElement();

			if (projectRoot.getNodeName().equals("TINYINVENTOR")) {

				// load the canvas (or pages and page blocks if any) blocks from
				// the save file
				// also load drawers, or any custom drawers from file. if no
				// custom drawers
				// are present in root, then the default set of drawers is
				// loaded from
				// langDefRoot

				resetWorkspace();
				workspace.loadWorkspaceFrom(projectRoot, langDefRoot);

				workspaceLoaded = true;
				return true;
			} else {
				System.err.println("Unsupport file format of " + path);
			}

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return false;
	}

	/**
	 * Returns the JComponent of the entire workspace.
	 * 
	 * @return the JComponent of the entire workspace.
	 */
	public JComponent getWorkspacePanel() {
		return workspacePanel;
	}

	
	public static JButton getButton(String name) {
		JButton button = new JButton(name);
		button.setBackground(Color.BLACK);
		button.setForeground(Color.WHITE);
		button.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
		return button;
	}

	public static JLabel getLabel(String text) {
		JLabel label = new JLabel(text);
		label.setBackground(Color.BLACK);
		label.setForeground(Color.WHITE);
		label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
		return label;
	}

	/**
	 * Create the GUI and show it. For thread safety, this method should be
	 * invoked from the event-dispatching thread.
	 */
	private static void createAndShowGUI(final Controller wc) {
        System.out.println("Creating GUI...");
        
        // Create and set up the window.
        JFrame frame = new JFrame("TinyInventor");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        int inset = 50;
//        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        
        frame.setBounds(100, 100, 900, 600);
        
        final JLabel statusLabel = getLabel("");
        final Timer statusTimer = new Timer(4000, new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				statusLabel.setText("");
			}
		});
        statusTimer.setRepeats(false);
        
        // create save button
        JButton saveButton = getButton("Save");
        saveButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e){
            	if(wc.saveProjectToPath(PROJECT_FILE_NAME)) {
            		statusLabel.setText("Saved!");	
            	} else {
            		statusLabel.setText("Cannot save!");
            	}
            	statusTimer.start();
            }
        });
        
        // create load button
        JButton loadButton = getButton("Load");
        loadButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e){
                if(wc.loadProjectFromPath(PROJECT_FILE_NAME)) {
                	statusLabel.setText("Loaded!");
                } else {
                	statusLabel.setText("Could not load!");
                }
                statusTimer.start();
            }
        });
        
        // create compile button
        JButton compileButton = getButton("Compile");
        compileButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event){
            	System.out.println("Compiling..");
                
				try {
					Compiler nescCompiler = new NescCompiler();
					nescCompiler.compilePage(Workspace.getInstance().getPageNamed("Mote"));
					Compiler pythonCompiler = new PythonCompiler();
					pythonCompiler.compilePage(Workspace.getInstance().getPageNamed("PC"));
					nescCompiler.build("output/mote");
					pythonCompiler.build("output/pc");
					statusLabel.setText("Compiled!");
					statusTimer.start();
				} catch (CompileException e) {
					e.printStackTrace();
					return;
				} catch (IOException e) {
					e.printStackTrace();
					return;
				}
                
                /*System.out.println("### Wiring ###: \n" + compiler.getWiringCode());
                System.out.println("### Interface ###: \n" + compiler.getInterfaceCode());
                System.out.println("### Module ###: \n" + compiler.getModuleCode());*/
                
            }
        });
        
        // create buildin button
        JButton buildinButton = getButton("Build-ins");
        buildinButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e){
                Workspace.getInstance().getFactoryManager().viewStaticDrawers();
            }
        });
        
        // create my decl button
        JButton myButton = getButton("My Declarations");
        myButton.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e){
        		Workspace.getInstance().getFactoryManager().viewDynamicDrawers();
        	}
        });
        
        // create search bar
        SearchBar searchBar = new SearchBar("Search blocks", "Search for blocks in the drawers and workspace", workspace);
    	for(SearchableContainer con : Workspace.getInstance().getAllSearchableContainers()){
    		searchBar.addSearchableContainer(con);
    	}
    	searchBar.getComponent().setPreferredSize(new Dimension(130, 23));
    	
    	JPanel leftPane = new JPanel();
    	leftPane.setBackground(Color.BLACK);
    	leftPane.add(buildinButton);
    	leftPane.add(myButton);
    	leftPane.add(saveButton);
    	leftPane.add(loadButton);
    	leftPane.add(compileButton);
    	leftPane.add(statusLabel);
    	
    	JPanel rightPane = new JPanel();
    	rightPane.setBackground(Color.BLACK);
    	rightPane.add(new ZoomSlider());
    	rightPane.add(searchBar.getComponent());
    	
        JPanel topPane = new JPanel(new BorderLayout());
        topPane.setBackground(Color.BLACK);
        topPane.add(leftPane, BorderLayout.WEST);
        topPane.add(rightPane, BorderLayout.EAST);
        
        frame.add(topPane, BorderLayout.PAGE_START);
        frame.add(wc.getWorkspacePanel(), BorderLayout.CENTER);
        
        frame.setVisible(true);
    }

	
	public static void main(String[] args) {
		// Schedule a job for the event-dispatching thread:
		// creating and showing this application's GUI.
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				// XML files
				String blocks = "res/blocks.xml";
				String nescComponents = "res/nesc_components.xml";
				String pythonComponents = "res/python_components.xml";

				// Create a new WorkspaceController
				Controller controller = new Controller();
				
				// Load workspace
				controller.setLangDefFilePath(blocks);
				controller.loadFreshWorkspace(true);
				
				// Create the GUI with workspace
				createAndShowGUI(controller);
				
				// Load compiler components
				NescCompiler.loadComponentsFromPath(nescComponents);
				PythonCompiler.loadComponentsFromPath(pythonComponents);
				
			}
		});
	}

}
